#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include "gameengine.hpp"
#include "../../window/windowevents.hpp"
#include "../../renderers/voxelrenderer.hpp"
#include "../../renderers/linerenderer.hpp"
#include "../../voxel/voxelchunks.hpp"

vx::GameEngine::GameEngine(
    const std::string& windowTitle
)
    :Engine(windowTitle)
{

}

vx::GameEngine::~GameEngine()
{
}

void vx::GameEngine::onStart()
{
    float vertices[] = {
        // x    y     z     u     v
       -1.0f,-1.0f, 0.0f, 0.0f, 0.0f,
        1.0f,-1.0f, 0.0f, 1.0f, 0.0f,
       -1.0f, 1.0f, 0.0f, 0.0f, 1.0f,

        1.0f,-1.0f, 0.0f, 1.0f, 0.0f,
        1.0f, 1.0f, 0.0f, 1.0f, 1.0f,
       -1.0f, 1.0f, 0.0f, 0.0f, 1.0f,
    };

    _shader = std::shared_ptr<Shader>(Shader::loadFromFile("../res/main.glslv", "../res/main.glslf"));
    _lineShader = std::shared_ptr<Shader>(Shader::loadFromFile(
        "../res/shaders/line.glslv",
        "../res/shaders/line.glslf"
    ));
    _texture = std::shared_ptr<Texture>(Texture::loadFromFile("../res/cyan_wool.png"));

    VoxelRenderer renderer;
    _chunks = std::shared_ptr<VoxelChunks>(new VoxelChunks());
    _chunks->addChunkAt(glm::ivec2(0, 0));
    _chunks->addChunkAt(glm::ivec2(1, 1));
    VoxelChunk* chunk1 = _chunks->getChunkAt(glm::ivec2(0, 0));
    VoxelChunk* chunk2 = _chunks->getChunkAt(glm::ivec2(1, 1));
    _chunkMeshes.resize(2);
    _chunkMeshes[0].first = chunk1;
    _chunkMeshes[0].second = renderer.renderChunk(*chunk1, *_chunks);
    _chunkMeshes[1].first = chunk2;
    _chunkMeshes[1].second = renderer.renderChunk(*chunk2, *_chunks);

    LineRenderer::addLine(
        glm::vec3(-0.5f, 0.5f, -0.5f),
        glm::vec3(-0.5f, 5.f, -0.5f)
    );

    // X
    LineRenderer::addLine(
        glm::vec3(-1000.f, 0.f, 0.f),
        glm::vec3(1000.f, 0.f, 0.f),
        glm::vec4(0, 1, 0, 1)
    );

    // Z
    LineRenderer::addLine(
        glm::vec3(0.f, 0.f, -1000.f),
        glm::vec3(0.f, 0.f, 1000.f),
        glm::vec4(0, 0, 1, 1)
    );

    glClearColor(0.6f, 0.62f, 0.65f, 1);

    glEnable(GL_DEPTH_TEST);
    glEnable(GL_CULL_FACE);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    const glm::u32vec2 windowSize = _window->getSize();
    _camera = std::make_shared<Camera>(glm::vec3(0, 0, 2), 70.0f, (float)windowSize.x / windowSize.y);
}

void vx::GameEngine::onUpdate()
{
    const float cameraMoveSpeed = 0.05f;
    if (WindowEvents::getKeyboardKeyState(GLFW_KEY_W).isHolding)
    {
        _camera->move(_camera->getFrontVec() * cameraMoveSpeed);
    }
    if (WindowEvents::getKeyboardKeyState(GLFW_KEY_S).isHolding)
    {
        _camera->move(-_camera->getFrontVec() * cameraMoveSpeed);
    }
    if (WindowEvents::getKeyboardKeyState(GLFW_KEY_A).isHolding)
    {
        _camera->move(-_camera->getRightVec() * cameraMoveSpeed);
    }
    if (WindowEvents::getKeyboardKeyState(GLFW_KEY_D).isHolding)
    {
        _camera->move(_camera->getRightVec() * cameraMoveSpeed);
    }
    const float cameraRotateSpeed = 0.002f;
    const glm::vec2 deltaMousePosition = WindowEvents::getDeltaMousePosition();
    _camera->rotate(
        deltaMousePosition.y * cameraRotateSpeed,
        deltaMousePosition.x * cameraRotateSpeed,
        0
    );
}

void vx::GameEngine::onRender()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    _shader->use();
    _shader->setUniformMatrix("projview", _camera->getProjectionViewMatrix());

    _texture->bind();
    for (auto& chunkMesh : _chunkMeshes)
    {
        const VoxelChunk* chunk = chunkMesh.first;
        const Mesh* mesh = chunkMesh.second.get();
        const glm::ivec2 chunkPosition = chunk->getPosition();
        glm::mat4 modelMatrix = glm::translate(
            glm::mat4(1.0f),
            glm::vec3(
                chunkPosition.x * VoxelChunk::SIZE,
                0,
                chunkPosition.y * VoxelChunk::SIZE
            )
        );
        _shader->setUniformMatrix("model", modelMatrix);
        mesh->draw();
    }

    _lineShader->use();
    _lineShader->setUniformMatrix("projview", _camera->getProjectionViewMatrix());
    LineRenderer::draw();
}
